04. MainCoroutineRule and Injecting Dispatchers
L5 P4 A04 MainCoroutineRule And Injecting Dispatchers V3
In this step you'll create the MainCoroutineRule JUnit rule and use it in TasksViewModelTest and DefaultTasksRepositoryTest.
Step 1: Add MainCoroutineRule
- Create a new class called MainCoroutineRule.kt in the root folder of the test source set.
- Copy over the following code to
MainCoroutineRule.kt:
MainCoroutineRule.kt
@ExperimentalCoroutinesApi
class MainCoroutineRule(val dispatcher: TestCoroutineDispatcher = TestCoroutineDispatcher()):
TestWatcher(),
TestCoroutineScope by TestCoroutineScope(dispatcher) {
override fun starting(description: Description?) {
super.starting(description)
Dispatchers.setMain(dispatcher)
}
override fun finished(description: Description?) {
super.finished(description)
cleanupTestCoroutines()
Dispatchers.resetMain()
}
}
Step 2: Use MainCoroutineRule in TasksViewModelTest
Now use your new rule.
- Open TasksViewModelTest.
- Replace
TestDispatchercode you just wrote (the@Beforeand@Aftercode to swap and cleanup the dispatcher) with code to use your newMainCoroutineRule:
TasksViewModelTest.kt
// REPLACE
@ExperimentalCoroutinesApi
val testDispatcher: TestCoroutineDispatcher = TestCoroutineDispatcher()
@ExperimentalCoroutinesApi
@Before
fun setupDispatcher() {
Dispatchers.setMain(testDispatcher)
}
@ExperimentalCoroutinesApi
@After
fun tearDownDispatcher() {
Dispatchers.resetMain()
testDispatcher.cleanupTestCoroutines()
}
// WITH
@ExperimentalCoroutinesApi
@get:Rule
var mainCoroutineRule = MainCoroutineRule()
- Run
completeTask_dataAndSnackbarUpdated, it should work exactly the same!
Step 3: Use MainCoroutineRule for repository testing.
You can also use this rule in DefaultTasksRepositoryTest.
- Open up test > data > source > DefaultTasksRepositoryTest.kt
- Add the
MainCoroutineRuleinside of the DefaultTasksRepositoryTest class:
DefaultTasksRepositoryTest.kt
// Set the main coroutines dispatcher for unit testing.
@ExperimentalCoroutinesApi
@get:Rule
var mainCoroutineRule = MainCoroutineRule()
Remember that MainCoroutineRule swaps the Dispatcher.Main for a TestCoroutineDispatcher.
- Use
Dispatcher.Main, instead ofDispatcher.Unconfinedwhen defining your repository under test:
DefaultTasksRepositoryTest.kt
@Before
fun createRepository() {
tasksRemoteDataSource = FakeDataSource(remoteTasks.toMutableList())
tasksLocalDataSource = FakeDataSource(localTasks.toMutableList())
// Get a reference to the class under test.
tasksRepository = DefaultTasksRepository(
// HERE Swap Dispatcher.Unconfined
tasksRemoteDataSource, tasksLocalDataSource, Dispatchers.Main
)
}
Generally, only create one TestCoroutineDispatcher to run a test. Whenever you call runBlockingTest, it will create a new TestCoroutineDispatcher if you don't specify one. MainCoroutineRule includes a TestCoroutineDispatcher. So, to ensure that you don't accidentally create multiple instances of TestCoroutineDispatcher, use the mainCoroutineRule.runBlockingTest instead of just runBlockingTest.
- Replace
runBlockingTestwithmainCoroutineRule.runBlockingTest:
DefaultTasksRepositoryTest.kt
// REPLACE
fun getTasks_requestsAllTasksFromRemoteDataSource() = runBlockingTest {
// WITH
fun getTasks_requestsAllTasksFromRemoteDataSource() = mainCoroutineRule.runBlockingTest {
- Run your
DefaultTasksRepositoryTestclass and confirm everything works as before!
Awesome job! Now you're using TestCoroutineDispatcher in your code, which is a preferable dispatcher for testing. Next you'll see how to use an additional feature of the TestCoroutineDispatcher, controlling coroutine execution timing.
MainCoroutineRule and many of the other concepts covered here are explained in detail in the talk Testing Coroutines on Android.